---
sidebar_position: 17
description: Use queryable projections to map queryable objects and optimize ORM performance
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# IQueryable projections

Mapperly does support `IQueryable<T>` projections:

<Tabs>
  <TabItem value="definition" label="Mapper definition">
  
  ```csharp
  [Mapper]
  public static partial class CarMapper
  {
      // highlight-start
      public static partial IQueryable<CarDto> ProjectToDto(this IQueryable<Car> q);
      // highlight-end
  }
  ```
  
  </TabItem>
  <TabItem value="usage" label="Usage">
  
  ```csharp
  var dtos = await DbContext.Cars
      .Where(...)
      // highlight-start
      .ProjectToDto()
      // highlight-end
      .ToListAsync();
  ```
  
  </TabItem>
</Tabs>

This is useful in combination with Entity Framework and other ORM solutions which expose `IQueryable<T>`.
Only fields present in the target class will be retrieved from the database.

:::info

Since queryable projection mappings use `System.Linq.Expressions.Expression<T>` under the hood,
such mappings have several limitations:

- Object factories are not applied
- Constructors with unmatched optional parameters are ignored
- `ThrowOnPropertyMappingNullMismatch` is ignored
- `AllowNullPropertyAssignment` is ignored
- Enum mappings do not support the `ByName` strategy
- Reference handling is not supported
- Nullable reference types are disabled
- Deep cloning is not applied
  :::

## Property configurations

To configure property mappings add partial mapping method definitions with attributes as needed.
Set these methods to private to hide them from callers.

```csharp
[Mapper]
public static partial class CarMapper
{
    // highlight-start
    public static partial IQueryable<CarDto> ProjectToDto(this IQueryable<Car> q);
    // highlight-end

    // highlight-start
    [MapProperty(nameof(Car.Manufacturer), nameof(CarDto.Producer)]
    // highlight-end
    private static partial CarDto Map(Car car);
}
```

## User-implemented mapping methods

Mapperly tries to inline user-implemented mapping methods.
For this to work, user-implemented mapping methods need to satisfy certain limitations:

- Only expression-bodied methods or methods that consist of a single local variable declaration expression can be inlined.
- The body needs to follow the [expression tree limitations](https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/expression-trees/#limitations).
- Nested MethodGroups cannot be inlined.

```csharp
[Mapper]
public static partial class CarMapper
{
    public static partial IQueryable<CarDto> ProjectToDto(this IQueryable<Car> q);

    // highlight-start
    private static string MapCarBrandName(CarBrand brand)
      => brand.Name;
    // highlight-end
}
```

If Mapperly is unable to inline a user-implemented method, `RMG068` is reported.
Non-inlined method invocations can lead to more data being loaded than necessary.

Ordering and aggregating collections can also be implemented with user-implemented mapping methods:

```csharp
[Mapper]
public static partial class CarMapper
{
    public static partial IQueryable<CarDto> ProjectToDto(this IQueryable<Car> q);

    private static partial CarModelDto MapCarModel(CarModel model);

    // highlight-start
    private static ICollection<CarModelDto> MapOrderedCarBrandName(ICollection<CarModel> models)
      // note: do not use the method group '.Select(MapCarModel)', as that cannot be inlined
      => models.OrderBy(x => x.Name).Select(x => MapCarModel(x)).ToList();
    // highlight-end
  }
```

It is important that the types in the user-implemented mapping method match the types of the objects to be mapped exactly.
Otherwise, Mapperly cannot resolve the user-implemented mapping methods.
